﻿using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using DSInternals.Common.Data;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DSInternals.Common.Test
{
    [TestClass]
    public class KeyCredentialTester
    {
        private const string DummyDN = "CN=Account,DC=contoso,DC=com";

        [TestMethod]
        public void KeyCredential_Parse_NonMFAKey()
        {
            // Dummy key incorrectly used by the official ADComputerKeys module to "delete" existing values from registered devices.
            byte[] blob = "000200002000010D76D33954251DA969022D0D3B009939E256A6C9B3FF657907C72063F89AE79E200002F6B00E6A9BA3066ABDE0E4B23EB82D5E42898263AD46CA84BE0CFD20E81F91C00E01033082010A0282010100D6589A6FE210490583C1DCD57E3579AB24979D9B1A7118E3553DEDCFFA5CF5ABD41CF6C19CBBE598CE6F9140541E8FF8A778BD5CAADD8D038A49785A4D9031C98E26783E824BA3CF00D86C112A9A5C65A5ACF2B077E365D947BD41A437E7034CC00A77550B2EA8CEC18C1F7516DA4DC13177E1DE1D32FBBDDE1E1FD7395AAB71A8F302B985A64248C3A239E6943AEAFA9A8B591AE499F31723F7DC8A22A6D197445056DA4DF9D13443DB4A6201D52D82795A2F2FFA2F75B6F2605E213609A39DF33F26E023D83D9C4BDDD4879E234407833BA38460CBC66D9D31CDF2C5B3A042F321DA7F2140ECC4A5A190306ED51FE0EA5273DD83D5338B2554ABD3738A06A50203010001010004010100050002000701020800086254F138261CD3010800096254F138261CD301".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.NGC, key.Usage);
            Assert.AreEqual(KeySource.AD, key.Source);
            Assert.IsTrue(key.CustomKeyInfo.Flags.HasFlag(KeyFlags.MFANotUsed));

            Assert.IsNotNull(key.RSAPublicKey);
            Assert.IsNotNull(key.RSAModulus);
            Assert.AreEqual(256, key.RSAPublicKey.Value.Modulus.Length);

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKey1()
        {
            byte[] blob = "0002000020000120717AE052FCCF546AAD0D51E878AAD69CE04FDC39F5A8D8E3CEBA6BCB4DA0E720000214B7474E61C1001D3E546CFED8E387CFC1AC86A2CA7B3CDCF1267614585E2A341B0103525341310008000003000000000100000000000000000000010001C1A78914457758B0B13C70C710C7F8548F3F9ED56AD4640B6E6A112655C98ECAC1CBD68A298F5686C08439428A97FE6FDF58D78EA481905182BAD684C2D9C5CDE1CDE34AA19742E8BBF58B953EAC4C562FCF598CC176B02DBE9FFFEF5937A65815C236F92892F7E511A1FEDD5483CB33F1EA715D68106180DED2432A293367114A6E325E62F93F73D7ECE4B6A2BCDB829D95C8645C3073B94BA7CB7515CD29042F0967201C6E24A77821E92A6C756DF79841ACBAAE11D90CA03B9FCD24EF9E304B5D35248A7BD70557399960277058AE3E99C7C7E2284858B7BF8B08CDD286964186A50A7FCBCC6A24F00FEE5B9698BBD3B1AEAD0CE81FEA461C0ABD716843A50100040101000500100006E377F547D0D20A4A8ACAE0501098BDE40200070100080008417BD66E6603D401080009417BD66E6603D401".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.NGC, key.Usage);
            Assert.AreEqual(KeySource.AD, key.Source);
            Assert.AreEqual("IHF64FL8z1RqrQ1R6Hiq1pzgT9w59ajY4866a8tNoOc=", key.Identifier);
            Assert.AreEqual("47f577e3-d2d0-4a0a-8aca-e0501098bde4", key.DeviceId.ToString());
            Assert.IsNotNull(key.CustomKeyInfo);

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKey2()
        {
            byte[] blob = "000200002000013845C226E299D67EFB43D7504DA462E2D951B517124E5D41A9B0C61E5A15B978200002A2E324776A66AED61D60C771DE3B1B8AA38CF260B63083DB1DA554F233FBF92B1B0103525341310008000003000000000100000000000000000000010001BF723DF58198223D30D10EF3335B1360453A89C57D4B8F0CCE3F958F834F50A01A069E3D92AE0DE07C92A43DF405AC756FFE2C97801E879CED5B0E25E052CEBF352C605C36BF87A2CFC16F830ABCB5A14DDC3EE282313ABE7049C55F2D37164BD050A20C8E5F6CD4B9EDDEC523836EA8DDF0E94ECE5B87A4B6541811312FED6BA0A118E174CCA19352C1A0DB704B9E789C086FB58543554746F4DFCDDD8E5DFEA2A548788DC340FD806A6D6ED6F2003B9E1447AF6A4040FBB2802D9093C3EB432BB72B8F033887555F60E70B927CB6C1FEC2BF17C03FCA03B3BAA56FB4F2A1ECCCD33B6C6AFCBB29CB65304E5894FDD77FD3982D1FB2B2AEAC6B5451F14A1A8F01000401010005011000064B5E981009083D448679149422D9E1700600070100000100000800080040230E430000400800097A9DBAB3B32AD548".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.NGC, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.IsTrue(key.CustomKeyInfo.SupportsNotification.Value);

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKey3()
        {
            byte[] blob = "00020000200001D333C85D306B27498806D9AEFDC984C18DE8EB855BF481E4AD3A24A21929FD5B200002E88E7EBE4CE413FDA9ED987C0CC8C327438B0861DC3BE116680CF8E2BAEB27B01B0103525341310008000003000000000100000000000000000000010001976D21DC9A0C0B84040688F5E7F2BB8147B1305CA01CEFDB13E9FAB49EB6734FD3C32B5D34B01EB6ACE35DDF73E62CB506501A5FD1AAAB698FB98AEA2F2721393C155D84DDF59EF91D8F6402FD755D246C3E04BAF96EFA04BBC7DD314C083800B934B192EA587904C938255D781EC0B2FE8FA3135F952A13FF805492579AD6710051525A7A824A8A5CBA74EF4D3A2F2E271856FF633A411912A53BEAA2805A1B57148ACC8404B473FD3580F450DE5AAB10334FEB084B6045A65840898A66BF88AE19DB802AF7FA4AEED95ECDC8FF286AE0075575F82974396B72730C15C511A961BBD6A5A4B46D395AA85F82ACBD585CE57DAE05EE7B22CBEA9E9E02571EF5890100040101000501100006871059FD5C24F54FA5EAC14DE5E2B32D05000701000000000800080040230E43000040080009D1B7948179CED448".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.NGC, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.None, key.CustomKeyInfo.Flags);

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKey4()
        {
            byte[] blob = "00020000200001B172047DE6F5926155BEEF8A10E7F16453C371696E61927257E7DE1876ADBE1920000248054C70147BF28076C655A54D7493FFD723DE422E6DF8BFA89C059D8A3E84521B0103525341310008000003000000000100000000000000000000010001B11FDB9CC39BB2C09AA244E8794BF60C7C9A7348DE93E9F368D4FFA77E6B96BB81898D53DA004BA74EC5E5EAE8C67D6DBC126863A78C436357A6C0AF5AF0557E8B1C71319D98CF6ECABED321E0751EAC0FCD2808A02152B0D703AFE0B54C10132CE981E4088A28110F5A4743B5D5A7862A5EAED28F53F2413CE763BCD823EC81EB225EB6A9A9989006E36A574D3FFDBF62BE4BDC00F7014D2E59BFD4077285BE88232BABFC3AEF85E20D8E97C2A94F64902CC86ADC3A2C486CC7CA8D0B163DEB41F1F66D202382D1C5F7DAC30BF9CA6F26538E5E91F3E1CBD8818B58459676588913BABD84E1AE0C2CCF5A76326F81B063581468B55B3E015DDA17B30A436CA10100040101000501100006739E89E927DBF94AB7EBC4201D6577EB040007010000000800080040230E430000400800094C79579CD17CD448".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.NGC, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.None, key.CustomKeyInfo.Flags);

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKeyFIDO0()
        {
            byte[] blob = "000200001000015847BA3C54FEDCC4FEA49D957D1FF88D20000261D774442D358413F23535501330F1FCB80FAA161C090932FA706966C812B724C404037B2276657273696F6E223A312C226175746844617461223A224E577965314B435449626C70587836766B59494438625666614A326D48377957474577566664706F44494846414141414D766F726D6479654F554A586A354A4B4D4E49385152674145466848756A78552F747A452F7153646C5830662B49326C415149444A69414249566767684858674A303148324B3568387A473075642F762B4E6757724F504C726F6B3976364E436D3168666F766B695743435376764C507A456F667878324D67442F4F54337A676C5850587A6357464B36554C575863505A54305862364672614731685979317A5A574E795A585431222C22783563223A5B224D4949437644434341615367417749424167494541363377456A414E42676B71686B69473977304241517346414441754D5377774B6759445651514445794E5A64574A705932386756544A4749464A7662335167513045675532567961574673494451314E7A49774D44597A4D544167467730784E4441344D4445774D4441774D444261474138794D4455774D446B774E4441774D4441774D466F776254454C4D416B474131554542684D4355305578456A415142674E5642416F4D43566C31596D6C6A62794242516A45694D434147413155454377775A515856306147567564476C6A59585276636942426448526C6333526864476C76626A456D4D43514741315545417777645758566961574E764946557952694246525342545A584A70595777674E6A45334D7A41344D7A51775754415442676371686B6A4F5051494242676771686B6A4F50514D4242774E434141515A6E6F65634669323333446E75536B4B675268616C73776E2B79676B766472344A53506C746270584B354D786C7A56536757632B3978386D7A477973646242684565634C41596651597170564C57576F7348506F586F327777616A416942676B724267454541594C4543674945465445754D7934324C6A45754E4334784C6A51784E4467794C6A45754E7A4154426773724267454541594C6C4841494241515145417749454D444168426773724267454541594C6C4841454242415153424244364B356E636E6A6C4356342B53536A4453504545594D41774741315564457745422F7751434D4141774451594A4B6F5A496876634E4151454C425141446767454241436A727332662B30646A77346F6E7279702F32324164587867366135587978636F796248446A4B7537324532534E3971444773495A536644793338444446722F6246317332356A6F69753757413674796C4B4130486D45446C6F654A584A69576A76376832417A322F736971576E4A4F4C6963345845316C4143684A53325841716B536B39564647656C6733534C4F696966724265742B6562645177414C2B325146726352374A7258525147396B557937364F3256635367626450524F7348664F59657977617268616C7956535A2B364F4F594B2F512F444C49614F43306A58726E6B7A6D32796D4D5146516C4241497973725965454D3177786946627744742B6C416362634F4574484566355A6C576937356E557A6C576E386253782F35464F3454625A35684945635569475270694942454D525A6C4F496D345A49625A79636E2F764A4F465254567073305630533479677444633D225D2C22646973706C61794E616D65223A22597562696B65792035227D0100040701000501100006000000000000000000000000000000000F00070101000000000000000000000000000800080040230E430000400800099310993462F6D648".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.FIDO, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.Attestation, key.CustomKeyInfo.Flags);
            Assert.AreEqual("WEe6PFT+3MT+pJ2VfR/4jQ==", key.Identifier);
            var km = key.FidoKeyMaterial;
            Assert.AreEqual("Yubikey 5", km.DisplayName);
            var expectedRpIdHash = new byte[] { 0x35, 0x6c, 0x9e, 0xd4, 0xa0, 0x93, 0x21, 0xb9, 0x69, 0x5f, 0x1e, 0xaf, 0x91, 0x82, 0x03, 0xf1, 0xb5, 0x5f, 0x68, 0x9d, 0xa6, 0x1f, 0xbc, 0x96, 0x18, 0x4c, 0x15, 0x7d, 0xda, 0x68, 0x0c, 0x81 };
            Assert.AreEqual(expectedRpIdHash.ToHex(true), km.AuthenticatorData.RelyingPartyIdHash.ToHex(true));
            Assert.AreEqual(Data.Fido.AuthenticatorFlags.UserPresent | Data.Fido.AuthenticatorFlags.UserVerified | Data.Fido.AuthenticatorFlags.AttestationData | Data.Fido.AuthenticatorFlags.ExtensionData, km.AuthenticatorData.Flags);
            Assert.AreEqual((uint)0x32, km.AuthenticatorData.SignatureCount);
            Assert.AreEqual(new Guid("fa2b99dc-9e39-4257-8f92-4a30d23c4118"), km.AuthenticatorData.AttestedCredentialData.AaGuid);
            var expectedCredentialId = new byte[] { 0x58, 0x47, 0xba, 0x3c, 0x54, 0xfe, 0xdc, 0xc4, 0xfe, 0xa4, 0x9d, 0x95, 0x7d, 0x1f, 0xf8, 0x8d };
            Assert.AreEqual(expectedCredentialId.ToHex(true), km.AuthenticatorData.AttestedCredentialData.CredentialID.ToHex(true));
            var strAcd = km.AuthenticatorData.AttestedCredentialData.ToString();
            var expectedStrAcd = "AAGUID: fa2b99dc-9e39-4257-8f92-4a30d23c4118, CredentialID: 5847BA3C54FEDCC4FEA49D957D1FF88D, CredentialPublicKey: {1: 2, 3: -7, -1: 1, -2: h'8475E0274D47D8AE61F331B4B9DFEFF8D816ACE3CBAE893DBFA3429B585FA2F9', -3: h'92BEF2CFCC4A1FC71D8C803FCE4F7CE09573D7CDC5852BA50B59770F653D176F'}";
            Assert.AreEqual(expectedStrAcd, strAcd);
            var strExts = km.AuthenticatorData.Extensions.ToString();
            var expectedStrExts = "{\"hmac-secret\": true}";
            Assert.AreEqual(expectedStrExts, strExts);
            Assert.IsNull(key.RSAPublicKey);
            Assert.IsNotNull(key.ECPublicKey);
            Assert.AreEqual("nistP256", key.ECPublicKey.Value.Curve.Oid.FriendlyName);
            Assert.AreEqual("8475e0274d47d8ae61f331b4b9dfeff8d816ace3cbae893dbfa3429b585fa2f9", key.ECPublicKey.Value.Q.X.ToHex());
            Assert.AreEqual("92bef2cfcc4a1fc71d8c803fce4f7ce09573d7cdc5852ba50b59770f653d176f", key.ECPublicKey.Value.Q.Y.ToHex());

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKeyFIDO1()
        {
            byte[] blob = "0002000010000134A672EA255A6DC9664567A35325E5DA20000226806F834F0151F48999B5813F5890EA2EB6C5977799AC94A2E9B419A811C007CC04037B2276657273696F6E223A312C226175746844617461223A224E577965314B435449626C70587836766B59494438625666614A326D48377957474577566664706F44494846414141417750696745664F4D436B30566741595845522B65334830414544536D63756F6C576D334A5A6B566E6F314D6C3564716C415149444A694142495667677030704E77387A6A6A786B3254745A444B526D5372746F686664545973437751314971714874453333713469574341414F47494B5839634F6A7144433458446850394156577536504673374347364E746D637474314C4742714B4672614731685979317A5A574E795A585431222C22783563223A5B224D494943766A434341616167417749424167494564496239776A414E42676B71686B69473977304241517346414441754D5377774B6759445651514445794E5A64574A705932386756544A4749464A7662335167513045675532567961574673494451314E7A49774D44597A4D544167467730784E4441344D4445774D4441774D444261474138794D4455774D446B774E4441774D4441774D466F77627A454C4D416B474131554542684D4355305578456A415142674E5642416F4D43566C31596D6C6A62794242516A45694D434147413155454377775A515856306147567564476C6A59585276636942426448526C6333526864476C76626A456F4D43594741315545417777665758566961574E764946557952694246525342545A584A70595777674D546B314E5441774D7A67304D6A425A4D424D4742797147534D34394167454743437147534D34394177454841304941424A5664383633334A48307864652F396E4D547A476B36486A72726867516C57595644374F4973755832556E763164416D7157427051304B785338595246774B4531534B45315049704F5761634535534F38424E362B326A624442714D43494743537347415151426773514B416751564D53347A4C6A59754D5334304C6A45754E4445304F4449754D5334784D424D474379734741515142677555634167454242415144416755674D43454743797347415151426775556341514545424249454550696745664F4D436B30566741595845522B653348307744415944565230544151482F424149774144414E42676B71686B6947397730424151734641414F43415145414D567849674F6161556E34345A6F6D396166304B7147394A3635354F6855564256572B713041733641496F643341483562486232614459616B6549797942436E6E474D48544A7475656B627248625859584552496E34614B646B50534B6C79474C73412F412B5745692B4F416658724E56666A68726837694536787A71307367342F76564A6F7977653465414A783066532B446C3361787A545470596C37314E6337702F4E583669434D6D64696B30704175594A6567426354636B4533416F594567344B3939414D2F4A61614B49626C73624668382B334C786E656D654E663755774F637A614747766A5336557A475649304F6466396C4B6350497759687554784D3543614E4D58545A51377871342F79546643336B50577445346846543334554A4A666C5A42694C727847344F7359786B48772F6E35764B676D707370423347665975595457686B444B69453843597479673837673D3D225D2C22646973706C61794E616D65223A22597562694B6579204649444F32227D0100040701000501100006000000000000000000000000000000000F00070101000000000000000000000000000800080040230E43000040080009B6FB5FFC0506D748".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.FIDO, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.Attestation, key.CustomKeyInfo.Flags);
            Assert.AreEqual("NKZy6iVabclmRWejUyXl2g==", key.Identifier);
            var km = key.FidoKeyMaterial;
            Assert.AreEqual("YubiKey FIDO2", km.DisplayName);
            var expectedRpIdHash = new byte[] { 0x35, 0x6c, 0x9e, 0xd4, 0xa0, 0x93, 0x21, 0xb9, 0x69, 0x5f, 0x1e, 0xaf, 0x91, 0x82, 0x03, 0xf1, 0xb5, 0x5f, 0x68, 0x9d, 0xa6, 0x1f, 0xbc, 0x96, 0x18, 0x4c, 0x15, 0x7d, 0xda, 0x68, 0x0c, 0x81 };
            Assert.AreEqual(expectedRpIdHash.ToHex(true), km.AuthenticatorData.RelyingPartyIdHash.ToHex(true));
            Assert.AreEqual(Data.Fido.AuthenticatorFlags.UserPresent | Data.Fido.AuthenticatorFlags.UserVerified | Data.Fido.AuthenticatorFlags.AttestationData | Data.Fido.AuthenticatorFlags.ExtensionData, km.AuthenticatorData.Flags);
            Assert.AreEqual((uint)0xc0, km.AuthenticatorData.SignatureCount);
            Assert.AreEqual(new Guid("f8a011f3-8c0a-4d15-8006-17111f9edc7d"), km.AuthenticatorData.AttestedCredentialData.AaGuid);
            var expectedCredentialId = new byte[] { 0x34, 0xa6, 0x72, 0xea, 0x25, 0x5a, 0x6d, 0xc9, 0x66, 0x45, 0x67, 0xa3, 0x53, 0x25, 0xe5, 0xda };
            Assert.AreEqual(expectedCredentialId.ToHex(true), km.AuthenticatorData.AttestedCredentialData.CredentialID.ToHex(true));
            var strAcd = km.AuthenticatorData.AttestedCredentialData.ToString();
            var expectedStrAcd = "AAGUID: f8a011f3-8c0a-4d15-8006-17111f9edc7d, CredentialID: 34A672EA255A6DC9664567A35325E5DA, CredentialPublicKey: {1: 2, 3: -7, -1: 1, -2: h'A74A4DC3CCE38F19364ED643291992AEDA217DD4D8B02C10D48AAA1ED137DEAE', -3: h'0038620A5FD70E8EA0C2E170E13FD0155AEE8F16CEC21BA36D99CB6DD4B181A8'}";
            Assert.AreEqual(expectedStrAcd, strAcd);
            var strExts = km.AuthenticatorData.Extensions.ToString();
            var expectedStrExts = "{\"hmac-secret\": true}";
            Assert.AreEqual(expectedStrExts, strExts);
            Assert.IsNull(key.RSAPublicKey);
            Assert.IsNotNull(key.ECPublicKey);
            Assert.AreEqual("nistP256", key.ECPublicKey.Value.Curve.Oid.FriendlyName);
            Assert.AreEqual("a74a4dc3cce38f19364ed643291992aeda217dd4d8b02c10d48aaa1ed137deae", key.ECPublicKey.Value.Q.X.ToHex());
            Assert.AreEqual("0038620a5fd70e8ea0c2e170e13fd0155aee8f16cec21ba36d99cb6dd4b181a8", key.ECPublicKey.Value.Q.Y.ToHex());

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKeyFIDO2()
        {
            byte[] blob = "00020000460001E03437E7F93F660B0A57F4D31A90B8CFE175C382F8CD3DA1791CBED294DD55C4E56C356C9ED4A09321B9695F1EAF918203F1B55F689DA61FBC96184C157DDA680C81080100002000028250D5D1CA46DD0143BAC3E724D6DF7970D3A0A61A15DD3219242E2498D4303E4005037B2276657273696F6E223A312C226175746844617461223A224E577965314B435449626C70587836766B59494438625666614A326D48377957474577566664706F444948464141414243496832597876556F454A2F56334D4F78787965416E6B41527541304E2B66355032594C436C663030787151754D2F6864634F432B4D30396F586B6376744B553356584535577731624A37556F4A4D6875576C6648712B52676750787456396F6E615966764A595954425639326D674D675167424141436C415149444A69414249566767536F4C6A4F5279687A4557336C6A334F4573715450507A753157354D4C75395539572B464554316C4A44776957434461504B34663373326D50447442566968745652544669345467546E634C757A3862776735516F51584D304B4672614731685979317A5A574E795A585431222C22783563223A5B224D49494334544343416F696741774942416749424154414B42676771686B6A4F50515144416A43426744454C4D416B474131554542684D4356564D784554415042674E564241674D43453168636E6C735957356B4D524977454159445651514B44416C54623278764945746C65584D784544414F42674E564241734D42314A7662335167513045784654415442674E5642414D4D44484E76624739725A586C7A4C6D4E76625445684D42384743537147534962334451454A415259536147567362473941633239736232746C65584D75593239744D434158445445344D5445784D5445794E5449774D466F59447A49774E6A67784D4449354D5449314D6A4177576A43426B6A454C4D416B474131554542684D4356564D784554415042674E564241674D43453168636E6C735957356B4D524977454159445651514B44416C54623278764945746C65584D78496A416742674E564241734D475546316447686C626E52705932463062334967515852305A584E3059585270623234784654415442674E5642414D4D44484E76624739725A586C7A4C6D4E76625445684D42384743537147534962334451454A415259536147567362473941633239736232746C65584D75593239744D466B77457759484B6F5A497A6A3043415159494B6F5A497A6A30444151634451674145497634507453703476735A464E786F6F70316444536152766855334B546955636E3355775062385131644C5343376C704C4E3279584254594F595553396950756B6272477250394B47696676344D46555039545A78614F42334443423254416442674E5648513445466751554F2B625377472F79353742386E5A346F774343774451664946636777675A384741315564497753426C7A43426C4B474268715342677A43426744454C4D416B474131554542684D4356564D784554415042674E564241674D43453168636E6C735957356B4D524977454159445651514B44416C54623278764945746C65584D784544414F42674E564241734D42314A7662335167513045784654415442674E5642414D4D44484E76624739725A586C7A4C6D4E76625445684D42384743537147534962334451454A415259536147567362473941633239736232746C65584D755932397467676B417845646A6B6F2F30766F77774351594456523054424149774144414C42674E564851384542414D4342504177436759494B6F5A497A6A3045417749445277417752414967635242474C505557474A6456796D52514F326D79337864787136324F774E616D427A316D696A75372F6D454349423643372B7465546A6F41684754532B4954446544575459344575767159534D6D34706B4D695253334653225D2C22646973706C61794E616D65223A22536F6C6F4B65797320536F6C6F227D0100040701000501100006000000000000000000000000000000000F00070100000000000000000000000000000800080040230E43000040080009D73EB2400406D748".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.FIDO, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.None, key.CustomKeyInfo.Flags);
            Assert.AreEqual("4DQ35/k/ZgsKV/TTGpC4z+F1w4L4zT2heRy+0pTdVcTlbDVsntSgkyG5aV8er5GCA/G1X2idph+8lhhMFX3aaAyBCAEAAA==", key.Identifier);
            var km = key.FidoKeyMaterial;
            Assert.AreEqual("SoloKeys Solo", km.DisplayName);
            var expectedRpIdHash = new byte[] { 0x35, 0x6c, 0x9e, 0xd4, 0xa0, 0x93, 0x21, 0xb9, 0x69, 0x5f, 0x1e, 0xaf, 0x91, 0x82, 0x03, 0xf1, 0xb5, 0x5f, 0x68, 0x9d, 0xa6, 0x1f, 0xbc, 0x96, 0x18, 0x4c, 0x15, 0x7d, 0xda, 0x68, 0x0c, 0x81 };
            Assert.AreEqual(expectedRpIdHash.ToHex(true), km.AuthenticatorData.RelyingPartyIdHash.ToHex(true));
            Assert.AreEqual(Data.Fido.AuthenticatorFlags.UserPresent | Data.Fido.AuthenticatorFlags.UserVerified | Data.Fido.AuthenticatorFlags.AttestationData | Data.Fido.AuthenticatorFlags.ExtensionData, km.AuthenticatorData.Flags);
            Assert.AreEqual((uint)0x108, km.AuthenticatorData.SignatureCount);
            Assert.AreEqual(new Guid("8876631b-d4a0-427f-5773-0ec71c9e0279"), km.AuthenticatorData.AttestedCredentialData.AaGuid);
            var expectedCredentialId = "E03437E7F93F660B0A57F4D31A90B8CFE175C382F8CD3DA1791CBED294DD55C4E56C356C9ED4A09321B9695F1EAF918203F1B55F689DA61FBC96184C157DDA680C8108010000";
            Assert.AreEqual(expectedCredentialId, km.AuthenticatorData.AttestedCredentialData.CredentialID.ToHex(true));
            var strAcd = km.AuthenticatorData.AttestedCredentialData.ToString();
            var expectedStrAcd = "AAGUID: 8876631b-d4a0-427f-5773-0ec71c9e0279, CredentialID: E03437E7F93F660B0A57F4D31A90B8CFE175C382F8CD3DA1791CBED294DD55C4E56C356C9ED4A09321B9695F1EAF918203F1B55F689DA61FBC96184C157DDA680C8108010000, CredentialPublicKey: {1: 2, 3: -7, -1: 1, -2: h'4A82E3391CA1CC45B7963DCE12CA933CFCEED56E4C2EEF54F56F85113D65243C', -3: h'DA3CAE1FDECDA63C3B4156286D5514C58B84E04E770BBB3F1BC20E50A105CCD0'}";
            Assert.AreEqual(expectedStrAcd, strAcd);
            var strExts = km.AuthenticatorData.Extensions.ToString();
            var expectedStrExts = "{\"hmac-secret\": true}";
            Assert.AreEqual(expectedStrExts, strExts);
            Assert.IsNull(key.RSAPublicKey);
            Assert.IsNotNull(key.ECPublicKey);
            Assert.AreEqual("nistP256", key.ECPublicKey.Value.Curve.Oid.FriendlyName);
            Assert.AreEqual("4a82e3391ca1cc45b7963dce12ca933cfceed56e4c2eef54f56f85113d65243c", key.ECPublicKey.Value.Q.X.ToHex());
            Assert.AreEqual("da3cae1fdecda63c3b4156286d5514c58b84e04e770bbb3f1bc20e50a105ccd0", key.ECPublicKey.Value.Q.Y.ToHex());

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKeyFIDO3()
        {
            byte[] blob = "00020000460001E54D1CFBC76CA6A746178E601D8B33B13A4E5F210D1B97F760F5C0E4CAFF761CAF7F356C9ED4A09321B9695F1EAF918203F1B55F689DA61FBC96184C157DDA680C810F010000200002FC02803C68BAA0FF7B6EA6B41A8D13FA11AB7C53BD4EE2E064935AED9ED3018D4805037B2276657273696F6E223A312C226175746844617461223A224E577965314B435449626C70587836766B59494438625666614A326D48377957474577566664706F444948464141414244346832597876556F454A2F56334D4F78787965416E6B415275564E48507648624B616E5268654F5942324C4D374536546C38684452755839324431774F544B2F33596372333831624A37556F4A4D6875576C6648712B52676750787456396F6E615966764A595954425639326D674D675138424141436C415149444A694142495667675146336E32333574534D4F725450553250506C7A6F6A3165304D4963687A497349694F774D5436546A777769574341515A486D3362634C46574B3545305074466C535454624859574B7A494D36574D5235566D67596C6C3938364672614731685979317A5A574E795A585431222C22783563223A5B224D49494334544343416F696741774942416749424154414B42676771686B6A4F50515144416A43426744454C4D416B474131554542684D4356564D784554415042674E564241674D43453168636E6C735957356B4D524977454159445651514B44416C54623278764945746C65584D784544414F42674E564241734D42314A7662335167513045784654415442674E5642414D4D44484E76624739725A586C7A4C6D4E76625445684D42384743537147534962334451454A415259536147567362473941633239736232746C65584D75593239744D434158445445344D5445784D5445794E5449774D466F59447A49774E6A67784D4449354D5449314D6A4177576A43426B6A454C4D416B474131554542684D4356564D784554415042674E564241674D43453168636E6C735957356B4D524977454159445651514B44416C54623278764945746C65584D78496A416742674E564241734D475546316447686C626E52705932463062334967515852305A584E3059585270623234784654415442674E5642414D4D44484E76624739725A586C7A4C6D4E76625445684D42384743537147534962334451454A415259536147567362473941633239736232746C65584D75593239744D466B77457759484B6F5A497A6A3043415159494B6F5A497A6A30444151634451674145497634507453703476735A464E786F6F70316444536152766855334B546955636E3355775062385131644C5343376C704C4E3279584254594F595553396950756B6272477250394B47696676344D46555039545A78614F42334443423254416442674E5648513445466751554F2B625377472F79353742386E5A346F774343774451664946636777675A384741315564497753426C7A43426C4B474268715342677A43426744454C4D416B474131554542684D4356564D784554415042674E564241674D43453168636E6C735957356B4D524977454159445651514B44416C54623278764945746C65584D784544414F42674E564241734D42314A7662335167513045784654415442674E5642414D4D44484E76624739725A586C7A4C6D4E76625445684D42384743537147534962334451454A415259536147567362473941633239736232746C65584D755932397467676B417845646A6B6F2F30766F77774351594456523054424149774144414C42674E564851384542414D4342504177436759494B6F5A497A6A3045417749445277417752414967635242474C505557474A6456796D52514F326D79337864787136324F774E616D427A316D696A75372F6D454349423643372B7465546A6F41684754532B4954446544575459344575767159534D6D34706B4D695253334653225D2C22646973706C61794E616D65223A22536F6C6F4B65797320536F6C6F2054617020555342227D0100040701000501100006000000000000000000000000000000000F00070100000000000000000000000000000800080040230E43000040080009E5C7225C0406D748".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.FIDO, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.None, key.CustomKeyInfo.Flags);
            Assert.AreEqual("5U0c+8dspqdGF45gHYszsTpOXyENG5f3YPXA5Mr/dhyvfzVsntSgkyG5aV8er5GCA/G1X2idph+8lhhMFX3aaAyBDwEAAA==", key.Identifier);
            var km = key.FidoKeyMaterial;
            Assert.AreEqual("SoloKeys Solo Tap USB", km.DisplayName);
            var expectedRpIdHash = new byte[] { 0x35, 0x6c, 0x9e, 0xd4, 0xa0, 0x93, 0x21, 0xb9, 0x69, 0x5f, 0x1e, 0xaf, 0x91, 0x82, 0x03, 0xf1, 0xb5, 0x5f, 0x68, 0x9d, 0xa6, 0x1f, 0xbc, 0x96, 0x18, 0x4c, 0x15, 0x7d, 0xda, 0x68, 0x0c, 0x81 };
            Assert.AreEqual(expectedRpIdHash.ToHex(true), km.AuthenticatorData.RelyingPartyIdHash.ToHex(true));
            Assert.AreEqual(Data.Fido.AuthenticatorFlags.UserPresent | Data.Fido.AuthenticatorFlags.UserVerified | Data.Fido.AuthenticatorFlags.AttestationData | Data.Fido.AuthenticatorFlags.ExtensionData, km.AuthenticatorData.Flags);
            Assert.AreEqual((uint)0x10f, km.AuthenticatorData.SignatureCount);
            Assert.AreEqual(new Guid("8876631b-d4a0-427f-5773-0ec71c9e0279"), km.AuthenticatorData.AttestedCredentialData.AaGuid);
            var expectedCredentialId = "E54D1CFBC76CA6A746178E601D8B33B13A4E5F210D1B97F760F5C0E4CAFF761CAF7F356C9ED4A09321B9695F1EAF918203F1B55F689DA61FBC96184C157DDA680C810F010000";
            Assert.AreEqual(expectedCredentialId, km.AuthenticatorData.AttestedCredentialData.CredentialID.ToHex(true));
            var strAcd = km.AuthenticatorData.AttestedCredentialData.ToString();
            var expectedStrAcd = "AAGUID: 8876631b-d4a0-427f-5773-0ec71c9e0279, CredentialID: E54D1CFBC76CA6A746178E601D8B33B13A4E5F210D1B97F760F5C0E4CAFF761CAF7F356C9ED4A09321B9695F1EAF918203F1B55F689DA61FBC96184C157DDA680C810F010000, CredentialPublicKey: {1: 2, 3: -7, -1: 1, -2: h'405DE7DB7E6D48C3AB4CF5363CF973A23D5ED0C21C87322C2223B0313E938F0C', -3: h'106479B76DC2C558AE44D0FB459524D36C76162B320CE96311E559A062597DF3'}";
            Assert.AreEqual(expectedStrAcd, strAcd);
            var strExts = km.AuthenticatorData.Extensions.ToString();
            var expectedStrExts = "{\"hmac-secret\": true}";
            Assert.AreEqual(expectedStrExts, strExts);
            Assert.IsNull(key.RSAPublicKey);
            Assert.IsNotNull(key.ECPublicKey);
            Assert.AreEqual("nistP256", key.ECPublicKey.Value.Curve.Oid.FriendlyName);
            Assert.AreEqual("405de7db7e6d48c3ab4cf5363cf973a23d5ed0c21c87322c2223b0313e938f0c", key.ECPublicKey.Value.Q.X.ToHex());
            Assert.AreEqual("106479b76dc2c558ae44d0fb459524d36c76162b320ce96311e559a062597df3", key.ECPublicKey.Value.Q.Y.ToHex());

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKeyFIDO4()
        {
            byte[] blob = "0002000020000158E5D17B2A43ED041284D1D98E428E8A3A99DEFA11D950C32C8AC9351A0599DD2000025979A72A31CCA7C1ECC5EB2639BC526A73B2FD59F6783988EDBCFCC765FBE1B87904037B2276657273696F6E223A312C226175746844617461223A224E577965314B435449626C70587836766B59494438625666614A326D48377957474577566664706F444948464141414135784C653130564C375566557136726E452F556459354D4149466A6C30587371512B3045456F5452325935436A6F6F366D64373645646C517779794B79545561425A6E64705145434179596741534659494C745A3667486C453775476357415036566A4939594F51336C45485961766F34636B572B73744B5363624A496C67674A6C3476414452415449556A4B7A6E2F43687656556C6F733661364F6D692F513163504265713047537879686132687459574D746332566A636D563039513D3D222C22783563223A5B224D494943517A434341656D67417749424167495148664B31576C4863533269466F396D6561582F74466A414B42676771686B6A4F50515144416A424A4D517377435159445651514745774A56557A45644D4273474131554543677755526D567064476C68626942555A574E6F626D3973623264705A584D78477A415A42674E5642414D4D456B5A6C6158527059573467526B6C4554794244515341774D7A4167467730784F4445794D6A55774D4441774D444261474138794D444D7A4D5449794E44497A4E546B314F566F776344454C4D416B474131554542684D4356564D784854416242674E5642416F4D46455A6C61585270595734675647566A614735766247396E6157567A4D534977494159445651514C44426C426458526F5A57353061574E6864473979494546306447567A64474630615739754D523477484159445651514444425647564342436157395159584E7A49455A4A52453879494441304E7A41775754415442676371686B6A4F5051494242676771686B6A4F50514D4242774E43414153363268496279656E483957506E7A594865686142523343377173776F6D5A6B61507A4779556C4652694A494D6F3375495465496D464F46664E6344754F7A6F71317763585847546D45744574784632776F396E6F6B6F34474A4D4947474D4230474131556444675157424253424931586F4C4459312F484A6162612B5733326E7868787033576A416642674E5648534D454744415767425242742F784E6463714F30703873307865627A594E52696E6E597154414D42674E5648524D4241663845416A41414D424D474379734741515142677555634167454242415144416752774D434547437973474151514267755563415145454242494545424C653130564C375566557136726E452F556459354D77436759494B6F5A497A6A304541774944534141775251496841493647535669313072363733757174736F2B326F4236663553356745306666343474334E63512B544E394E416941432F5343502B654B7731426E6D63536762786351705975576A42504D5644667165673870626D4F64484B773D3D222C226F416A713243654E41322F6779356D7863357456597835527845614C7A4B44316C61303761677466476F303D225D2C22646973706C61794E616D65223A224665697469616E20416C6C696E50617373204649444F32227D0100040701000501100006000000000000000000000000000000000F000701010000000000000000000000000008000800000000000000400800092EB826B0572AD748".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.FIDO, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.Attestation, key.CustomKeyInfo.Flags);
            Assert.AreEqual("WOXReypD7QQShNHZjkKOijqZ3voR2VDDLIrJNRoFmd0=", key.Identifier);
            var km = key.FidoKeyMaterial;
            Assert.AreEqual("Feitian AllinPass FIDO2", km.DisplayName);
            var expectedRpIdHash = new byte[] { 0x35, 0x6c, 0x9e, 0xd4, 0xa0, 0x93, 0x21, 0xb9, 0x69, 0x5f, 0x1e, 0xaf, 0x91, 0x82, 0x03, 0xf1, 0xb5, 0x5f, 0x68, 0x9d, 0xa6, 0x1f, 0xbc, 0x96, 0x18, 0x4c, 0x15, 0x7d, 0xda, 0x68, 0x0c, 0x81 };
            Assert.AreEqual(expectedRpIdHash.ToHex(true), km.AuthenticatorData.RelyingPartyIdHash.ToHex(true));
            Assert.AreEqual(Data.Fido.AuthenticatorFlags.UserPresent | Data.Fido.AuthenticatorFlags.UserVerified | Data.Fido.AuthenticatorFlags.AttestationData | Data.Fido.AuthenticatorFlags.ExtensionData, km.AuthenticatorData.Flags);
            Assert.AreEqual((uint)0xe7, km.AuthenticatorData.SignatureCount);
            Assert.AreEqual(new Guid("12ded745-4bed-47d4-abaa-e713f51d6393"), km.AuthenticatorData.AttestedCredentialData.AaGuid);
            var expectedCredentialId = "58E5D17B2A43ED041284D1D98E428E8A3A99DEFA11D950C32C8AC9351A0599DD";
            Assert.AreEqual(expectedCredentialId, km.AuthenticatorData.AttestedCredentialData.CredentialID.ToHex(true));
            var strAcd = km.AuthenticatorData.AttestedCredentialData.ToString();
            var expectedStrAcd = "AAGUID: 12ded745-4bed-47d4-abaa-e713f51d6393, CredentialID: 58E5D17B2A43ED041284D1D98E428E8A3A99DEFA11D950C32C8AC9351A0599DD, CredentialPublicKey: {1: 2, 3: -7, -1: 1, -2: h'BB59EA01E513BB8671600FE958C8F58390DE510761ABE8E1C916FACB4A49C6C9', -3: h'265E2F0034404C85232B39FF0A1BD5525A2CE9AE8E9A2FD0D5C3C17AAD064B1C'}";
            Assert.AreEqual(expectedStrAcd, strAcd);
            var strExts = km.AuthenticatorData.Extensions.ToString();
            var expectedStrExts = "{\"hmac-secret\": true}";
            Assert.AreEqual(expectedStrExts, strExts);
            Assert.IsNull(key.RSAPublicKey);
            Assert.IsNotNull(key.ECPublicKey);
            Assert.AreEqual("nistP256", key.ECPublicKey.Value.Curve.Oid.FriendlyName);
            Assert.AreEqual("bb59ea01e513bb8671600fe958c8f58390de510761abe8e1c916facb4a49c6c9", key.ECPublicKey.Value.Q.X.ToHex());
            Assert.AreEqual("265e2f0034404c85232b39ff0a1bd5525a2ce9ae8e9a2fd0d5c3c17aad064b1c", key.ECPublicKey.Value.Q.Y.ToHex());

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKeyFIDO5()
        {
            byte[] blob = "0002000020000175A1B961F58ECD81E97A9522DD1198FBDB3E6276516BCCE58182DB4E1407B53920000214AA4B99A1325D54E4F6CCB0B5B4A620F18F35B77D44B3AA14559544A7C52CD07704037B2276657273696F6E223A312C226175746844617461223A224E577965314B435449626C70587836766B59494438625666614A326D48377957474577566664706F444948464141414242586342433963684B6B2F4A736A6253796C36645149514149485768755748316A73324236587156497430526D507662506D4A325557764D3559474332303455423755357051454341795967415346594941736F744A752B63324350753276684B36513159446F416F79794743424857634D594639546E694937674B496C676761765870376235762F376A6B4762344C393042774F2B6A396A3978376A5545394256713349526B47657043686132687459574D746332566A636D563039513D3D222C22783563223A5B224D494943517A434341656967417749424167495148664B31576C4863533269466F396D6561582F744644414B42676771686B6A4F50515144416A424A4D517377435159445651514745774A56557A45644D4273474131554543677755526D567064476C68626942555A574E6F626D3973623264705A584D78477A415A42674E5642414D4D456B5A6C6158527059573467526B6C4554794244515341774D544167467730784F4441324D6A45774D4441774D444261474138794D444D7A4D4459794D44497A4E546B314F566F77627A454C4D416B474131554542684D4356564D784854416242674E5642416F4D46455A6C61585270595734675647566A614735766247396E6157567A4D534977494159445651514C44426C426458526F5A57353061574E6864473979494546306447567A64474630615739754D523077477759445651514444425247564342436157395159584E7A49455A4A5245387949465654516A425A4D424D4742797147534D34394167454743437147534D34394177454841304941424742512B4737684A4E6B576864557A495548524C2B354E6E68646432775344486E4B74696C763944594C506E6731466137466467616974645631744C446F6E6A6758504742346E36626C32644775593172697476304B6A67596B7767595977485159445652304F4242594546414879777254637556353768682F6254706E4B574A3833657A50314D42384741315564497751594D42614146482F736C50394B75534E67364256626A4C3037525642557878776B4D41774741315564457745422F7751434D4141774577594C4B77594242414743355277434151454542414D43425341774951594C4B775942424147433552774241515145456751516477454C3179457154386D794E744C4B587031416844414B42676771686B6A4F5051514441674E4A41444247416945416A51342F5472462F714B384C5A38486B4D6D6D43556E653075514945347549366D6635336666572F2F573043495144373377594F6F50724C376865496263676137666D316B6A5370386A7A556863774C64716145747773355A413D3D222C22304152566965435064796F33645839466C4579396D753775416357336C663354667347694F6457794C586B3D225D2C22646973706C61794E616D65223A224665697469616E2042696F50617373204649444F32227D0100040701000501100006000000000000000000000000000000000F00070101000000000000000000000000000800080000000000000040080009E187922E582AD748".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.FIDO, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.Attestation, key.CustomKeyInfo.Flags);
            Assert.AreEqual("daG5YfWOzYHpepUi3RGY+9s+YnZRa8zlgYLbThQHtTk=", key.Identifier);
            var km = key.FidoKeyMaterial;
            Assert.AreEqual("Feitian BioPass FIDO2", km.DisplayName);
            var expectedRpIdHash = new byte[] { 0x35, 0x6c, 0x9e, 0xd4, 0xa0, 0x93, 0x21, 0xb9, 0x69, 0x5f, 0x1e, 0xaf, 0x91, 0x82, 0x03, 0xf1, 0xb5, 0x5f, 0x68, 0x9d, 0xa6, 0x1f, 0xbc, 0x96, 0x18, 0x4c, 0x15, 0x7d, 0xda, 0x68, 0x0c, 0x81 };
            Assert.AreEqual(expectedRpIdHash.ToHex(true), km.AuthenticatorData.RelyingPartyIdHash.ToHex(true));
            Assert.AreEqual(Data.Fido.AuthenticatorFlags.UserPresent | Data.Fido.AuthenticatorFlags.UserVerified | Data.Fido.AuthenticatorFlags.AttestationData | Data.Fido.AuthenticatorFlags.ExtensionData, km.AuthenticatorData.Flags);
            Assert.AreEqual((uint)0x105, km.AuthenticatorData.SignatureCount);
            Assert.AreEqual(new Guid("77010bd7-212a-4fc9-b236-d2ca5e9d4084"), km.AuthenticatorData.AttestedCredentialData.AaGuid);
            var expectedCredentialId = "75A1B961F58ECD81E97A9522DD1198FBDB3E6276516BCCE58182DB4E1407B539";
            Assert.AreEqual(expectedCredentialId, km.AuthenticatorData.AttestedCredentialData.CredentialID.ToHex(true));
            var strAcd = km.AuthenticatorData.AttestedCredentialData.ToString();
            var expectedStrAcd = "AAGUID: 77010bd7-212a-4fc9-b236-d2ca5e9d4084, CredentialID: 75A1B961F58ECD81E97A9522DD1198FBDB3E6276516BCCE58182DB4E1407B539, CredentialPublicKey: {1: 2, 3: -7, -1: 1, -2: h'0B28B49BBE73608FBB6BE12BA435603A00A32C860811D670C605F539E223B80A', -3: h'6AF5E9EDBE6FFFB8E419BE0BF740703BE8FD8FDC7B8D413D055AB72119067A90'}";
            Assert.AreEqual(expectedStrAcd, strAcd);
            var strExts = km.AuthenticatorData.Extensions.ToString();
            var expectedStrExts = "{\"hmac-secret\": true}";
            Assert.AreEqual(expectedStrExts, strExts);
            Assert.IsNull(key.RSAPublicKey);
            Assert.IsNotNull(key.ECPublicKey);
            Assert.AreEqual("nistP256", key.ECPublicKey.Value.Curve.Oid.FriendlyName);
            Assert.AreEqual("0b28b49bbe73608fbb6be12ba435603a00a32c860811d670c605f539e223b80a", key.ECPublicKey.Value.Q.X.ToHex());
            Assert.AreEqual("6af5e9edbe6fffb8e419be0bf740703be8fd8fdc7b8d413d055ab72119067a90", key.ECPublicKey.Value.Q.Y.ToHex());

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKeyFIDO6()
        {
            byte[] blob = "0002000020000115DB7508D22047C329C0CDFB295EC0B44B8D141C123FAAACB3F0588221C1C8C9200002E2E0A732152FF3954D38309B28DB9435EC2D1E6CA24776F9C10AEAEB096626BCDB04037B2276657273696F6E223A312C226175746844617461223A224E577965314B435449626C70587836766B59494438625666614A326D48377957474577566664706F4449484641414141424A56454B793778586B3376736E447673516236793034414942586264516A53494566444B63444E2B796C65774C524C6A525163456A2B71724C50775749496877636A4A705145434179596741534659494F6F70745834704337494668303878366C664E6F696667662F6A4B62646A46657670576858383035594A72496C6767413061594363316B645541512B574932545367637936355536356A366D7067496C6741544836352B2F5232686132687459574D746332566A636D563039513D3D222C22783563223A5B224D49494374444343416C6D67417749424167494A414C78654D3769336870516F4D416F4743437147534D343942414D434D4947764D517377435159445651514745774A4C556A45524D4138474131554543417749553256766457777455326B78457A415242674E564241634D436B6468626D64755957307452335578467A415642674E5642416F4D446D5658516B3067513238754C43424D644751754D534977494159445651514C44426C426458526F5A57353061574E6864473979494546306447567A64474630615739754D527777476759445651514444424E6C56304A4E49454E4249454E6C636E52705A6D6C6A5958526C4D5230774777594A4B6F5A496876634E41516B4246673570626D5A765147557464324A744C6D4E7662544165467730784F5441314D444D774E6A49324D544E61467730794F5441304D6A6B774E6A49324D544E614D4947784D517377435159445651514745774A4C556A45524D4138474131554543417749553256766457777455326B78457A415242674E564241634D436B6468626D64755957307452335578467A415642674E5642416F4D446D5658516B3067513238754C43424D644751754D534977494159445651514C44426C426458526F5A57353061574E6864473979494546306447567A64474630615739754D523877485159445651514444425A6C56304A4E49455A4A5245387949454E6C636E52705A6D6C6A5958526C4D5277774767594A4B6F5A496876634E41516B4246673170626D5A7651475633596D3075593239744D466B77457759484B6F5A497A6A3043415159494B6F5A497A6A304441516344516741454255336D56717A3153723967543173454F77616E7965614B6133736945764E765A35762B534C65394863367A734C686D4F50394663464D3051336E30713153356D4D7A304943765A5449776F5055584259624139414B4E614D4667774351594456523054424149774144416642674E5648534D4547444157674253334A2F6678694176323269726442733938534F446846376B552F6A416442674E56485134454667515552433670576964574D6342476331793049416A6165647867596E4577437759445652305042415144416758674D416F4743437147534D343942414D4341306B414D455943495143675459704465345A373457726270546E305467757A53554D32496A4D5267527846384E536E686E624149674968414E3573535868396B59523733593336525356386A384D2B6E4A5737614B4F68527154505A39375666694379225D2C22646973706C61794E616D65223A226557424D20476F6C64656E676174652047333130227D0100040701000501100006000000000000000000000000000000000F000701010000000000000000000000000008000800000000000000400800091D848CAD832CD748".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.FIDO, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.Attestation, key.CustomKeyInfo.Flags);
            Assert.AreEqual("Fdt1CNIgR8MpwM37KV7AtEuNFBwSP6qss/BYgiHByMk=", key.Identifier);
            var km = key.FidoKeyMaterial;
            Assert.AreEqual("eWBM Goldengate G310", km.DisplayName);
            var expectedRpIdHash = new byte[] { 0x35, 0x6c, 0x9e, 0xd4, 0xa0, 0x93, 0x21, 0xb9, 0x69, 0x5f, 0x1e, 0xaf, 0x91, 0x82, 0x03, 0xf1, 0xb5, 0x5f, 0x68, 0x9d, 0xa6, 0x1f, 0xbc, 0x96, 0x18, 0x4c, 0x15, 0x7d, 0xda, 0x68, 0x0c, 0x81 };
            Assert.AreEqual(expectedRpIdHash.ToHex(true), km.AuthenticatorData.RelyingPartyIdHash.ToHex(true));
            Assert.AreEqual(Data.Fido.AuthenticatorFlags.UserPresent | Data.Fido.AuthenticatorFlags.UserVerified | Data.Fido.AuthenticatorFlags.AttestationData | Data.Fido.AuthenticatorFlags.ExtensionData, km.AuthenticatorData.Flags);
            Assert.AreEqual((uint)0x4, km.AuthenticatorData.SignatureCount);
            Assert.AreEqual(new Guid("95442b2e-f15e-4def-b270-efb106facb4e"), km.AuthenticatorData.AttestedCredentialData.AaGuid);
            var expectedCredentialId = "15DB7508D22047C329C0CDFB295EC0B44B8D141C123FAAACB3F0588221C1C8C9";
            Assert.AreEqual(expectedCredentialId, km.AuthenticatorData.AttestedCredentialData.CredentialID.ToHex(true));
            var strAcd = km.AuthenticatorData.AttestedCredentialData.ToString();
            var expectedStrAcd = "AAGUID: 95442b2e-f15e-4def-b270-efb106facb4e, CredentialID: 15DB7508D22047C329C0CDFB295EC0B44B8D141C123FAAACB3F0588221C1C8C9, CredentialPublicKey: {1: 2, 3: -7, -1: 1, -2: h'EA29B57E290BB205874F31EA57CDA227E07FF8CA6DD8C57AFA56857F34E5826B', -3: h'03469809CD64754010F962364D281CCBAE54EB98FA9A98089600131FAE7EFD1D'}";
            Assert.AreEqual(expectedStrAcd, strAcd);
            var strExts = km.AuthenticatorData.Extensions.ToString();
            var expectedStrExts = "{\"hmac-secret\": true}";
            Assert.AreEqual(expectedStrExts, strExts);
            Assert.IsNull(key.RSAPublicKey);
            Assert.IsNotNull(key.ECPublicKey);
            Assert.AreEqual("nistP256", key.ECPublicKey.Value.Curve.Oid.FriendlyName);
            Assert.AreEqual("ea29b57e290bb205874f31ea57cda227e07ff8ca6dd8c57afa56857f34e5826b", key.ECPublicKey.Value.Q.X.ToHex());
            Assert.AreEqual("03469809cd64754010f962364d281ccbae54eb98fa9a98089600131fae7efd1d", key.ECPublicKey.Value.Q.Y.ToHex());

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_UserKeyFIDO7()
        {
            byte[] blob = "00020000200001A9DBBA0041B1AF13223BD391465B0F79242148A9B87DF37B15361799B51E98F3200002447341C48A99598520CF5ECC79CFD14240C91AEFCCD990EE2535C0575026C0ACDB04037B2276657273696F6E223A312C226175746844617461223A224E577965314B435449626C70587836766B59494438625666614A326D48377957474577566664706F4449484641414141553466627861464D6C453349696B655832414439487A7741494B6E627567424273613854496A76546B555A6244336B6B495569707548337A6578553246356D3148706A7A7051454341795967415346594942497342767132614D73396355696452763946587A63437962754C4C6C3247474B57484C707A5249374D38496C67676842544D6E486C3264516F77484F577874633558784656374B416E64504530454D4B6D62755045616D4269686132687459574D746332566A636D563039513D3D222C22783563223A5B224D494943736A4343416C6D67417749424167494A41494F505347786A7A656B5A4D416F4743437147534D343942414D434D4947764D517377435159445651514745774A4C556A45524D4138474131554543417749553256766457777455326B78457A415242674E564241634D436B6468626D64755957307452335578467A415642674E5642416F4D446D5658516B3067513238754C43424D644751754D534977494159445651514C44426C426458526F5A57353061574E6864473979494546306447567A64474630615739754D527777476759445651514444424E6C56304A4E49454E4249454E6C636E52705A6D6C6A5958526C4D5230774777594A4B6F5A496876634E41516B4246673570626D5A765147557464324A744C6D4E7662544165467730784F5441314D444D774E6A4D784E445261467730794F5441304D6A6B774E6A4D784E4452614D4947784D517377435159445651514745774A4C556A45524D4138474131554543417749553256766457777455326B78457A415242674E564241634D436B6468626D64755957307452335578467A415642674E5642416F4D446D5658516B3067513238754C43424D644751754D534977494159445651514C44426C426458526F5A57353061574E6864473979494546306447567A64474630615739754D523877485159445651514444425A6C56304A4E49455A4A5245387949454E6C636E52705A6D6C6A5958526C4D5277774767594A4B6F5A496876634E41516B4246673170626D5A7651475633596D3075593239744D466B77457759484B6F5A497A6A3043415159494B6F5A497A6A304441516344516741454B2F437152654E5273786646316C684B5952424F6B4E797478477A384C74684B45746E43334C664C2B306B6B34387964382B682F306E46517A5552495666793673594578524E39794C66786A63734838364F447267614E614D4667774351594456523054424149774144416642674E5648534D4547444157674253334A2F6678694176323269726442733938534F446846376B552F6A416442674E5648513445466751556F6D6C2B4236356535534D73662F4B7047704951476B7445424B6377437759445652305042415144416758674D416F4743437147534D343942414D43413063414D45514349414D574C31537339704A666C48457552525A325058494E6D76775139724E4D36356B6465352B6B4253674A41694146463450336C61787132354A5057783779316F53694368635957674876524E4A75632F58394352707A5A673D3D225D2C22646973706C61794E616D65223A2265574D4220476F6C64656E676174652047333230227D0100040701000501100006000000000000000000000000000000000F00070101000000000000000000000000000800080000000000000040080009CE643C66842CD748".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.FIDO, key.Usage);
            Assert.AreEqual(KeySource.AzureAD, key.Source);
            Assert.AreEqual(KeyFlags.Attestation, key.CustomKeyInfo.Flags);
            Assert.AreEqual("qdu6AEGxrxMiO9ORRlsPeSQhSKm4ffN7FTYXmbUemPM=", key.Identifier);
            var km = key.FidoKeyMaterial;
            Assert.AreEqual("eWMB Goldengate G320", km.DisplayName);
            var expectedRpIdHash = new byte[] { 0x35, 0x6c, 0x9e, 0xd4, 0xa0, 0x93, 0x21, 0xb9, 0x69, 0x5f, 0x1e, 0xaf, 0x91, 0x82, 0x03, 0xf1, 0xb5, 0x5f, 0x68, 0x9d, 0xa6, 0x1f, 0xbc, 0x96, 0x18, 0x4c, 0x15, 0x7d, 0xda, 0x68, 0x0c, 0x81 };
            Assert.AreEqual(expectedRpIdHash.ToHex(true), km.AuthenticatorData.RelyingPartyIdHash.ToHex(true));
            Assert.AreEqual(Data.Fido.AuthenticatorFlags.UserPresent | Data.Fido.AuthenticatorFlags.UserVerified | Data.Fido.AuthenticatorFlags.AttestationData | Data.Fido.AuthenticatorFlags.ExtensionData, km.AuthenticatorData.Flags);
            Assert.AreEqual((uint)0x53, km.AuthenticatorData.SignatureCount);
            Assert.AreEqual(new Guid("87dbc5a1-4c94-4dc8-8a47-97d800fd1f3c"), km.AuthenticatorData.AttestedCredentialData.AaGuid);
            var expectedCredentialId = "A9DBBA0041B1AF13223BD391465B0F79242148A9B87DF37B15361799B51E98F3";
            Assert.AreEqual(expectedCredentialId, km.AuthenticatorData.AttestedCredentialData.CredentialID.ToHex(true));
            var strAcd = km.AuthenticatorData.AttestedCredentialData.ToString();
            var expectedStrAcd = "AAGUID: 87dbc5a1-4c94-4dc8-8a47-97d800fd1f3c, CredentialID: A9DBBA0041B1AF13223BD391465B0F79242148A9B87DF37B15361799B51E98F3, CredentialPublicKey: {1: 2, 3: -7, -1: 1, -2: h'122C06FAB668CB3D71489D46FF455F3702C9BB8B2E5D8618A5872E9CD123B33C', -3: h'8414CC9C7976750A301CE5B1B5CE57C4557B2809DD3C4D0430A99BB8F11A9818'}";
            Assert.AreEqual(expectedStrAcd, strAcd);
            var strExts = km.AuthenticatorData.Extensions.ToString();
            var expectedStrExts = "{\"hmac-secret\": true}";
            Assert.AreEqual(expectedStrExts, strExts);
            Assert.IsNull(key.RSAPublicKey);
            Assert.IsNotNull(key.ECPublicKey);
            Assert.AreEqual("nistP256", key.ECPublicKey.Value.Curve.Oid.FriendlyName);
            Assert.AreEqual("122c06fab668cb3d71489d46ff455f3702c9bb8b2e5d8618a5872e9cd123b33c", key.ECPublicKey.Value.Q.X.ToHex());
            Assert.AreEqual("8414cc9c7976750a301ce5b1b5ce57c4557b2809dd3c4d0430a99bb8f11a9818", key.ECPublicKey.Value.Q.Y.ToHex());

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        /* For EdDSA keys, if they become available and are supported
        [TestMethod]
        public void KeyCredential_Parse_UserKeyFIDO_EdDSA()
        {
            var EdDSABytes = new byte [] { 0xA4, 0x01, 0x01, 0x03, 0x27, 0x20, 0x06, 0x21, 0x58, 0x20, 0x01, 0x8F, 0xC5, 0x82, 0xA0, 0x68, 0xD4, 0xBF, 0x43, 0x24, 0x7B, 0x04, 0x8F, 0x65, 0xEE, 0xC2, 0x4B, 0xC2, 0xA7, 0xDD, 0x80, 0x94, 0x4E, 0xC0, 0x48, 0x24, 0xE6, 0x1F, 0x22, 0xDF, 0x90, 0xA1 };
            var EdDSA = PeterO.Cbor.CBORObject.DecodeFromBytes(EdDSABytes);
            var cpk = new Data.Fido.CredentialPublicKey(EdDSA);
            Assert.AreEqual(Data.Fido.COSE.Algorithm.EdDSA, cpk.Alg);
            Assert.AreEqual(Data.Fido.COSE.KeyType.OKP, cpk.Type);
            Assert.IsNull(cpk.ECDsa);
            Assert.IsNull(cpk.RSA);
            Assert.IsNotNull(cpk.EdDSAPublicKey);
            Assert.AreEqual(0x20, cpk.EdDSAPublicKey.Length);
            var expectedEdDSAKey = new byte[] { 0x01, 0x8F, 0xC5, 0x82, 0xA0, 0x68, 0xD4, 0xBF, 0x43, 0x24, 0x7B, 0x04, 0x8F, 0x65, 0xEE, 0xC2, 0x4B, 0xC2, 0xA7, 0xDD, 0x80, 0x94, 0x4E, 0xC0, 0x48, 0x24, 0xE6, 0x1F, 0x22, 0xDF, 0x90, 0xA1 };
            Assert.AreEqual(expectedEdDSAKey.ToHex(true), cpk.EdDSAPublicKey.ToHex(true));
        }
        */

        [TestMethod]
        public void KeyCredential_Parse_DeviceKey_RSA()
        {
            byte[] blob = "0002000020000173E6BEB8A9B5B0828388476E7BFDD5F8E7A113EC0807EF25C0FBCF39CEB4311120000299DA9872C6EB63882C1200B3B2BECCF3C582418F9FC56905963ADA62E52DF3B31B0103525341310008000003000000000100000000000000000000010001B40D7085917A30D2F0D434FEF57477099FFFEBC79F28EB414BB75C86B4B5CAC0D9E6ACA86EB8126EDB724AF40FD773A7F14732A7ED862A0828A367194FB3D61EC6EA15CB450597F3BAA64E4974B255D0819E06B58B47C858C384B88E27D0EA52F962A592B115EEA3AA21A6A5185DD58F5D779118717FD07C8CAF50F5F078BFC3AED355BB2F78E8C48C4F6DA2BD679CDCD1C0ED8320F5BC9EC6545E4E7CD9AA7642E180E2A3AD20BCCCF3C30A34BEDF27835528BE955A7599D42869339218936E78FF6D46BEEE0097F2DECB2791F7842BB55BA639A44F659F547B5AA1E959370ACBC908248D05893D539F7E4E6BE834CCF0A3101879717585D015992B3C9407410100040201000500100006E377F547D0D20A4A8ACAE0501098BDE40200070100080008405E47D3C301D401080009405E47D3C301D401".HexToBinary();
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.STK, key.Usage);
            Assert.AreEqual(KeySource.AD, key.Source);
            Assert.AreEqual("c+a+uKm1sIKDiEdue/3V+OehE+wIB+8lwPvPOc60MRE=", key.Identifier);
            Assert.IsNotNull(key.CustomKeyInfo);
            Assert.IsNotNull(key.RSAPublicKey);

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_DeviceKey_TPM()
        {
            byte[] blob = "000200002000013A86065B23BCC4190BC77C76774425B4C6AFF7EE35958D2C1B128830F17D89FD2000029D737FFB5C056741291CBAD933714FA911446B958E8FA3A802A96414B64849B40403035043504D3800000002000000020000003C010000E00000000000000000000000B00000000000000000000000000000000000000000000000013A0001000B0003047200209DFFCBF36C383AE699FB9868DC6DCB89D7153884BE2803922C124158BFAD22AE00060080004300100800000000000100A88E8812C2C35E42CFFF100024CAAEFD12C04929DF925FD5AB7F26B3DF92CEA60410B9792B789BB122323502CCE394A8CC246D01ACA04E5327C4D79F3339FA54F61F25F126649ECF3A70B7847915222BD54E470550E2449F9FDB43B2EB55377971935C9727DE419E2A9CCBDCE6A97F0F84F56272E65B9920AE16DE3AF350F7CC29C6A492BB1D703CC0AD23253D0E828FFBDBEAC21E0E0C1705A5E4894264EA357A13D36A62B74CB362705CB35244DB61FABF8B584C57F22F571858594D046485A50BF0D84D68EB69C1C8F92F99FE07080E2AC69A77DA12097F70B8102D7DDA19EB6C60DD2828815E4A0008A0E2A0BF363720B49F897B0D3109B2A4BC119A3AF700DE0020F9EDC3869F8ED64C5DC24366AFC3288DAB0E00A1673C2170C43F733B728282860010BA276857DCACF6D1A1C18FF161089380D8736D754A12C81D004B8296A5F077A0649E44540775FC179A03D0B75B81DB194AF3F47ACC4BB228C181065FEDDA86BB2DF704261391217662C49C7A719E08F535467CD217D645C6AF31CAC3D8350735CA358D006448276105C8392978CB1BEC89A515D0AB402DF476947061F8581CE91C5AB75919E5110B027A1809A581F0224FED448E8A1932AFE2954B0224B6512F8223678079EFD2B25255FE2D829D514C12C95A79E31C949C341D0000000600208FCD2169AB92694E0C633F1AB772842B8241BBC20288981FC7AC1EDDC1FDDB0E0020E529F5D6112872954E8ED6605117B757E237C6E19513A949FEE1F204C458023A0020AF2CA569699C436A21006F1CB8A2756C98BC1C765A3559C5FE1C3F5E7228A7E70020C413A847B11112B1CBDDD4ECA4DAAA15A1852C1C3BBA57461D257605F3D5AF5300000020048E9A3ACE08583F79F344FF785BBEA9F07AC7FA3325B3D49A21DD5194C65850010004020100050010000672A8E9CFFF135147A777AEC88C30A76202000701000800086885D2B95D49D5010800096885D2B95D49D501".HexToBinary();

            // Parse
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.STK, key.Usage);
            Assert.AreEqual(KeySource.AD, key.Source);
            Assert.AreEqual(KeyFlags.None, key.CustomKeyInfo.Flags);
            Assert.AreEqual("OoYGWyO8xBkLx3x2d0QltMav9+41lY0sGxKIMPF9if0=", key.Identifier);
            Assert.AreEqual(key.LastLogonTime, key.CreationTime);
            Assert.AreEqual("2019-08-02T18:11:37.5665512Z", key.CreationTime.ToUniversalTime().ToString("o"));
            Assert.AreEqual(0x304, key.RawKeyMaterial.Length);

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Parse_ComputerKey()
        {
            byte[] blob = "000200002000019C00E026B615793DE47951FF58A15F1F967297980C3EDAAF60B9E08FC9986F1320000204B8D485B4691C934E291D38B873B78390D4074B5D5391A851BF12C7466FEEC40E01033082010A0282010100B851C9219527F52E8A51582243E2CCA390B634FE5DE16B2BCA2E225257F3FF20BFE478C98B36095C49D897D42A67E2545D77003D38B9DF18682AF6FBFF281895CE61DADD5F72E13B40DA34E47833D380E58175F7D509DFA5E9971068756626AF1425B7CE0393BDB28AFF8E25CC601DE4542672E723B5BBB4E7D3963C2ACFB445171B43C14683DF0ED6524BD11F583D5BBEEBBA1DE6DE3384DF598E0D8BADACFBF1667890DC72CE61AF746084364BC288D982F23A6CD123E9BB6B701E00B096BE899876FE93BDD8B1C56FC107F36F7B2C8CE1AFB715FCDECA192634BE961B6104F21BFD84C97305123FF69D05D685CC8760CE54D9788457882D9DD39AFDA1D77D0203010001010004010100050008000831FF708C3402D401080009A962ABD55AF6D301".HexToBinary();

            // Parse
            var key = new KeyCredential(blob, DummyDN);
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.NGC, key.Usage);
            Assert.AreEqual(KeySource.AD, key.Source);
            Assert.IsNull(key.CustomKeyInfo);
            Assert.IsNotNull(key.RSAPublicKey);

            // Serialize
            byte[] serialized = key.ToByteArray();
            Assert.AreEqual(blob.Length, serialized.Length);
            CollectionAssert.AreEqual(blob, serialized);
        }

        [TestMethod]
        public void KeyCredential_Generate_UserKey_FromCertificate()
        {
            // Input
            byte[] inputBinaryCertificate = "308205473082042fa00302010202104dc51a1f078cd9834fb0523834a5402e300d06092a864886f70d010105050030820149318201453082014106035504031e8201380053002d0031002d0035002d00320031002d0032003800390034003400380039003800340037002d0031003100330036003400320038003400340037002d0032003100340033003000390038003800370034002d0031003100300036002f00380032003200370063006600650061002d0061006600350033002d0034006200370035002d0061003400650033002d006500660035003800320061003500320037003400310065002f006c006f00670069006e002e00770069006e0064006f00770073002e006e00650074002f00330038003300610033003800380039002d0035006200630039002d0034003700610033002d0038003400360063002d003200620037003000660030006200370066006500300065002f00610064006d0069006e00400063006f006e0074006f0073006f002e0063006f006d301e170d3138303631333232323233345a170d3438303631333232333233345a30820149318201453082014106035504031e8201380053002d0031002d0035002d00320031002d0032003800390034003400380039003800340037002d0031003100330036003400320038003400340037002d0032003100340033003000390038003800370034002d0031003100300036002f00380032003200370063006600650061002d0061006600350033002d0034006200370035002d0061003400650033002d006500660035003800320061003500320037003400310065002f006c006f00670069006e002e00770069006e0064006f00770073002e006e00650074002f00330038003300610033003800380039002d0035006200630039002d0034003700610033002d0038003400360063002d003200620037003000660030006200370066006500300065002f00610064006d0069006e00400063006f006e0074006f0073006f002e0063006f006d30820122300d06092a864886f70d01010105000382010f003082010a0282010100c1a78914457758b0b13c70c710c7f8548f3f9ed56ad4640b6e6a112655c98ecac1cbd68a298f5686c08439428a97fe6fdf58d78ea481905182bad684c2d9c5cde1cde34aa19742e8bbf58b953eac4c562fcf598cc176b02dbe9fffef5937a65815c236f92892f7e511a1fedd5483cb33f1ea715d68106180ded2432a293367114a6e325e62f93f73d7ece4b6a2bcdb829d95c8645c3073b94ba7cb7515cd29042f0967201c6e24a77821e92a6c756df79841acbaae11d90ca03b9fcd24ef9e304b5d35248a7bd70557399960277058ae3e99c7c7e2284858b7bf8b08cdd286964186a50a7fcbcc6a24f00fee5b9698bbd3b1aead0ce81fea461c0abd716843a50203010001a3273025300c0603551d130101ff0402300030150603551d25040e300c060a2b060104018237140202300d06092a864886f70d01010505000382010100435e76ffbe9b75052f1b96f67439d3970821f17be454703cb36be91d4c4de349a7d841b266412fa4235254d774f0a224708a4af78e6dce3fd42a2f89365323c951762a38e8b3d2ba0dd0971bf1cb0ecaa17cd82cdb64b969df48419aa4f28cc2d8c91112274ae7ba2f4a0db3c55b7b34ad0a9a5dc56f195208cb7440e9a51bd6996422a31c1632be6335300460538b80e7282f1bb5331cdbda2a182fe33bef9980e6a5257d264e13749036d508da8c29cde953cb747330a99483111abe69a49d11f426f30514505afe25e28a0ec5011e293bf3e13295f7d89a532b35a9e6bb7166ee21247a8dd2000ee987464748b838aa689cc6a499ea2e6614bb100c3beea4".HexToBinary();
            string expectedPublicKeyBlob = "525341310008000003000000000100000000000000000000010001C1A78914457758B0B13C70C710C7F8548F3F9ED56AD4640B6E6A112655C98ECAC1CBD68A298F5686C08439428A97FE6FDF58D78EA481905182BAD684C2D9C5CDE1CDE34AA19742E8BBF58B953EAC4C562FCF598CC176B02DBE9FFFEF5937A65815C236F92892F7E511A1FEDD5483CB33F1EA715D68106180DED2432A293367114A6E325E62F93F73D7ECE4B6A2BCDB829D95C8645C3073B94BA7CB7515CD29042F0967201C6E24A77821E92A6C756DF79841ACBAAE11D90CA03B9FCD24EF9E304B5D35248A7BD70557399960277058AE3E99C7C7E2284858B7BF8B08CDD286964186A50A7FCBCC6A24F00FEE5B9698BBD3B1AEAD0CE81FEA461C0ABD716843A5";
            var certificate = new X509Certificate2(inputBinaryCertificate);
            var expectedRSAParameters = ((RSACryptoServiceProvider)certificate.PublicKey.Key).ExportParameters(false);
            string expectedModulus = Convert.ToBase64String(expectedRSAParameters.Modulus);

            // Convert
            byte[] publicKeyBlob = certificate.ExportRSAPublicKeyBCrypt();
            var key = new KeyCredential(certificate, Guid.NewGuid(), DummyDN);

            // Check
            Assert.AreEqual(expectedPublicKeyBlob, publicKeyBlob.ToHex(true));
            CollectionAssert.AreEqual(expectedRSAParameters.Modulus, key.RSAPublicKey.Value.Modulus);
            CollectionAssert.AreEqual(expectedRSAParameters.Exponent, key.RSAPublicKey.Value.Exponent);
            Assert.AreEqual(expectedModulus, key.RSAModulus);
        }

        [TestMethod]
        public void KeyCredential_Generate_UserKey_FromPublicKey()
        {
            byte[] publicKey = "525341310008000003000000000100000000000000000000010001C1A78914457758B0B13C70C710C7F8548F3F9ED56AD4640B6E6A112655C98ECAC1CBD68A298F5686C08439428A97FE6FDF58D78EA481905182BAD684C2D9C5CDE1CDE34AA19742E8BBF58B953EAC4C562FCF598CC176B02DBE9FFFEF5937A65815C236F92892F7E511A1FEDD5483CB33F1EA715D68106180DED2432A293367114A6E325E62F93F73D7ECE4B6A2BCDB829D95C8645C3073B94BA7CB7515CD29042F0967201C6E24A77821E92A6C756DF79841ACBAAE11D90CA03B9FCD24EF9E304B5D35248A7BD70557399960277058AE3E99C7C7E2284858B7BF8B08CDD286964186A50A7FCBCC6A24F00FEE5B9698BBD3B1AEAD0CE81FEA461C0ABD716843A5".HexToBinary();
            Guid deviceId = Guid.Parse("47f577e3-d2d0-4a0a-8aca-e0501098bde4");
            DateTime creationTime = DateTime.FromFileTime(131734027581684545);
            string expectedKeyCredentialBlob = "0002000020000120717AE052FCCF546AAD0D51E878AAD69CE04FDC39F5A8D8E3CEBA6BCB4DA0E720000214B7474E61C1001D3E546CFED8E387CFC1AC86A2CA7B3CDCF1267614585E2A341B0103525341310008000003000000000100000000000000000000010001C1A78914457758B0B13C70C710C7F8548F3F9ED56AD4640B6E6A112655C98ECAC1CBD68A298F5686C08439428A97FE6FDF58D78EA481905182BAD684C2D9C5CDE1CDE34AA19742E8BBF58B953EAC4C562FCF598CC176B02DBE9FFFEF5937A65815C236F92892F7E511A1FEDD5483CB33F1EA715D68106180DED2432A293367114A6E325E62F93F73D7ECE4B6A2BCDB829D95C8645C3073B94BA7CB7515CD29042F0967201C6E24A77821E92A6C756DF79841ACBAAE11D90CA03B9FCD24EF9E304B5D35248A7BD70557399960277058AE3E99C7C7E2284858B7BF8B08CDD286964186A50A7FCBCC6A24F00FEE5B9698BBD3B1AEAD0CE81FEA461C0ABD716843A50100040101000500100006E377F547D0D20A4A8ACAE0501098BDE40200070100080008417BD66E6603D401080009417BD66E6603D401";

            var keyCredential = new KeyCredential(publicKey, deviceId, DummyDN, creationTime);

            byte[] keyCredentialBlob = keyCredential.ToByteArray();
            Assert.AreEqual(expectedKeyCredentialBlob, keyCredentialBlob.ToHex(true));
        }

        [TestMethod]
        public void KeyCredential_Generate_CompouterKey_FromPublicKey()
        {
            byte[] publicKey = "3082010a0282010100b851c9219527f52e8a51582243e2cca390b634fe5de16b2bca2e225257f3ff20bfe478c98b36095c49d897d42a67e2545d77003d38b9df18682af6fbff281895ce61dadd5f72e13b40da34e47833d380e58175f7d509dfa5e9971068756626af1425b7ce0393bdb28aff8e25cc601de4542672e723b5bbb4e7d3963c2acfb445171b43c14683df0ed6524bd11f583d5bbeebba1de6de3384df598e0d8badacfbf1667890dc72ce61af746084364bc288d982f23a6cd123e9bb6b701e00b096be899876fe93bdd8b1c56fc107f36f7b2c8ce1afb715fcdeca192634be961b6104f21bfd84c97305123ff69d05d685cc8760ce54d9788457882d9dd39afda1d77d0203010001".HexToBinary();
            var key = new KeyCredential(publicKey, null, DummyDN, DateTime.Now, true);

            Assert.IsNotNull(key.RSAPublicKey);
            Assert.IsNull(key.DeviceId);

            // Check DS-Validated-Write-Computer requirements
            // See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f70afbcc-780e-4d91-850c-cfadce5bb15c
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.NGC, key.Usage);
            Assert.AreEqual(KeySource.AD, key.Source);
            Assert.IsNull(key.CustomKeyInfo);
            Assert.IsNull(key.LastLogonTime);
        }

        [TestMethod]
        public void KeyCredential_Generate_CompouterKey_FromCertificate()
        {
            byte[] certificateBlob = "47000000010000002c000000480065006c006c006f002d005000430031002e0063006f006e0074006f0073006f002e0063006f006d0000000200000001000000fc0000001c000000ac0000000100000000000000000000000000000001000000660065003500350061003200370065006600640039006200300033003700620063003300320066006600330062003600350062003500610039003000320035005f00650064006300340036003400340030002d0036003500630039002d0034003100630065002d0061006100650062002d003700330037003500340065003000650033003800630038000000000000004d006900630072006f0073006f006600740020005300740072006f006e0067002000430072007900700074006f0067007200610070006800690063002000500072006f00760069006400650072000000030000000100000014000000dc22ad126560211021c25f0fe022b56897760bc85c0000000100000004000000000800001900000001000000100000006d7fd8ffd82d8d30efc6230ef38db6af140000000100000014000000b8402b86a77e453ce716c094685555fcb57caa200f0000000100000014000000bd8a7d086a23bfbe6bb0e8da8f6512ac7112e06d0400000001000000100000009163992bfd2e3967fc844f819c1534a8200000000100000027030000308203233082020ba00302010202102c11a1e99dd8f59b47e347583680e622300d06092a864886f70d010105050030393137303506035504030c2e532d312d352d32312d313233363432353237312d323838303734383436372d323539323638373432382d31313038301e170d3139303830343133333033325a170d3439303830343133343033305a30393137303506035504030c2e532d312d352d32312d313233363432353237312d323838303734383436372d323539323638373432382d3131303830820122300d06092a864886f70d01010105000382010f003082010a0282010100e583fb0c07be5fd56fcfe58a1cc08bfdb842e1642e17e0c9539c595a262d571d9404337969e8e8a764516b3f0feb638e5da9858e8d8683fc963decfb974a55231c2475b2b9b9ad73e4ed9d2e9892f654bdfef2420a891b968ef194b5d856048d410f854f16339537a2f6e30f4ae1ae58abb8e9438a5a92bcf3c5050374d3e7739a6c88a0e7231996d25533f2259f69592f311206e49a70fdcb88f30326251d2b273aa508a6b4b8f432264d58604909ad57da6209f12867b0c59a1fe2566665eb92c3bfd3a2dc0c69cc6dfb258b2ca7a148ac3438fd455f8789528843718bc48c3782ceeb4aed74b98bf422dcb3884fe3db8311feed479a823fbe9de334aaf17d0203010001a327302530150603551d25040e300c060a2b060104018237140202300c0603551d130101ff04023000300d06092a864886f70d010105050003820101007b97544471a1b78f82d58bc254a44018fc51356ef4e2536d3c3e7c6ef3a4edbc5e82fb4f026649b07416d5b9635b3052fc43b4d36158ed94c99893cb8a7b269a8fd56406a31b0787cbdd42a31d76740008ed8e657f82b7e267b174cba0aa303d963a536bf80aaef1c2bb8885d629a39398ed01bcf3c366eb7150189618eef1fd835bbd52d46f5c33709af029ea04d2b025d15262724462c4144465133318e9ef6377dbca5667c7ee99dabe6b156ef022e47dfa4dc9487c2f1f539099a330c18a18688a9f25930541c3bd275f4c67b4691cf714bb6d35593ccf94aace5490caf77262114711d307d38265a344b724f70d3414e79e56832090c2d795b867556cc4".HexToBinary();
            var certificate = new X509Certificate2(certificateBlob);
            var key = new KeyCredential(certificate, null, DummyDN, null, true);

            Assert.IsNotNull(key.RSAPublicKey);
            Assert.AreEqual(256, key.RSAPublicKey.Value.Modulus.Length);
            Assert.IsNull(key.DeviceId);
            Assert.IsFalse(key.RawKeyMaterial.IsBCryptRSAPublicKeyBlob());

            // Check DS-Validated-Write-Computer requirements
            // See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f70afbcc-780e-4d91-850c-cfadce5bb15c
            Assert.AreEqual(KeyCredentialVersion.Version2, key.Version);
            Assert.AreEqual(KeyUsage.NGC, key.Usage);
            Assert.AreEqual(KeySource.AD, key.Source);
            Assert.IsNull(key.CustomKeyInfo);
            Assert.IsNull(key.LastLogonTime);
        }
    }
}
